home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1996 / MacHack 1996.toast / Hacks / Hacks ’92 / AshtrayII / PigLatinTranslation.c next >
Encoding:
C/C++ Source or Header  |  1992-06-22  |  16.8 KB  |  561 lines  |  [TEXT/MPS ]

  1. /*
  2.     File:        SampleFileTranslationExt.c 
  3.  
  4.     Contains:    Sample code shell for writing a File Translation Extension.
  5.     
  6.     Written by:    Nick Kledzik & Dylan Ashe
  7.  
  8.     Copyright:    © 1991-1992 by Apple Computer, Inc., all rights reserved.
  9.  
  10. */
  11.  
  12.  
  13.  
  14. #include <Resources.h>
  15. #include "Components.h"
  16. #include "Errors.h"
  17. #include "Files.h"
  18. #include "TranslationExtensions.h"
  19.  
  20.  
  21. /*
  22.     Notes:    
  23.     
  24.         Errors:
  25.             All routines return OSErr's.  If they succeed they should return noErr.
  26.             The component manager requires all routines to return a ComponentResult
  27.             which is a 32-bit long to simplify dispatching.  If your code returns an OSErr
  28.             the compiler will automatically promote it to a long (ComponentResult).
  29.             
  30.         Globals:
  31.             Your routines can not use globals.  There really is no reason they should
  32.             need to use globals.  The three routines are separate and self contained.
  33.             If you persist and try to use PC relative globals, be warned that the 
  34.             ComponentMgr may purge and reload your code resource.  Therefore all
  35.             PC relative globals must be preinitailized at compile time (not load time).
  36.             Also, the ComponentMgr routine SetComponentInstanceStorage can not be
  37.             used, because InstanceStorage is already used by the TranslationMgr. 
  38.             
  39.         Memory allocation:
  40.             When one of your three routines is called, the default heap is the System
  41.             heap.  If you need to load any other code resources or allocate any memory
  42.             it is up to you to choose the best heap.  The System heap was choosen as
  43.             the default because many applications (including the Finder) have their
  44.             own memory management scheme that only leaves minimal free space in the 
  45.             application heap.  
  46.             
  47.         Resources:
  48.             If you need to access resources from your Translation Extension file, you
  49.             will need to use OpenComponentResFile() and CloseComponentResFile().
  50.             The open routine requires the ComponentInstance parameter supplied to 
  51.             your routine.
  52.             
  53.             
  54. */
  55.  
  56.  
  57.  
  58. /***********************************************************************************************
  59. *
  60. *    The purpose of DoGetFileTranslationList is to tell the TranslationMgr what file formats 
  61. *    this extension can translate between.
  62. *    
  63. *    You are given the list that you built for the TranslationMgr last time, or if this is
  64. *    the first time you've been called, the list if empty (groupCount = 0, modDate = 0).
  65. *    
  66. *    You need to figure out if the file formats we can translate between has changed.  For
  67. *    example, the user has installed more translators. One way to check this is to compare
  68. *    the modDate of the list with the modDate of the file/folder of the translators.
  69. *    If it has not changed, you just return without changing the list.
  70. *    
  71. *
  72. *    Catalog information about your available translators 
  73. *    
  74. *    Divide the translators into groups.  Within each group, any of the 'src' formats
  75. *    can used with an of the 'dst' formats (any reader can be connected to any writer).
  76. *    
  77. *    Resize the translationList handle to hold all the information about each group.
  78. *    Fill in the translationList.  
  79. *    
  80. *    Use the modDate field as a "version" of the list.  When you are called again, you can
  81. *    use it to quickly check if the list needs to be rebuilt.  The TranslationMgr checks the
  82. *    modDate of the list before and after call you DoGetFileTranslationList.  If the modDate
  83. *    has changed, the TranslationMgr updates its internal tables of translation info and saves
  84. *    the new list to disk for use next time that DoGetFileTranslationList is called.
  85. *        
  86. *    The groupCount field is the number of groups in the list.
  87. *    Each group consists of a list of 'src' and 'dst' FileTypeSpec's. Each FileTypeSpec
  88. *    contains the FileType of the file format that will be read or written, a 32-bit hint,
  89. *    translation attributes, and the canonical HFS type and creator.
  90. *    This hint is for use by your File Translation Extension.  It is intended to be used to
  91. *    quickly locate the required translator.  
  92. *
  93. *    An example translationList that converts from WriteNow™ to MacWrite™ 5.0 documents would
  94. *    look list this:  
  95. *    
  96. *        0xA5BC1234        ; modDate 
  97. *        1                ; only 1 group
  98. *        20                ; size of each entry = sizeof(FileTypeSpec)
  99. *        1                ; 1 source type
  100. *        'nX^d'            ; WriteNow FileType
  101. *        0                ; hint for WriteNow FileType (0=unused)
  102. *        0                ; no attributes
  103. *        'nX^d'            ; HFS type of WriteNow documents
  104. *        'nX^n'            ; HFS creator of WriteNow documents
  105. *        1                ; 1 destination type
  106. *        20                ; size of each entry = sizeof(FileTypeSpec)
  107. *        'WORD'            ; MacWrite 5.0 FileType
  108. *        0                ; hint for MacWrite 5.0 FileType (0=unused)
  109. *        0                ; no attributes
  110. *        'WORD'            ; HFS type of MacWrite 5.0 documents
  111. *        'MACA'            ; HFS creator of MacWrite 5.0 documents
  112. *
  113. *
  114. *    If you return an error, the TranslationMgr will ignore your translation list.  This will 
  115. *    effectively disable your extension from being called.  When the machine is restarted,
  116. *    your DoGetFileTranslationList will get called again.   
  117. *    
  118. ************************************************************************************************/
  119. pascal ComponentResult DoGetFileTranslationList(ComponentInstance self, 
  120.                                                 FileTranslationListHandle translationList)
  121. {
  122. #pragma unused(self)
  123.     FileTranslationListPtr    pList = *translationList;
  124.  
  125.     // the data never changes, so just check for initial empty list
  126.     if ( (pList->groupCount != 1) || (pList->modDate != 0xA58FA300) )
  127.     {
  128.         long*    pLong;
  129.         
  130.         pList->modDate = 0xA58FA300;    // about now
  131.         pList->groupCount = 1;
  132.          
  133.         SetHandleSize((Handle)translationList, sizeof(FileTranslationList) + 2 * (2*sizeof(FileTypeSpec)+8));
  134.         if (MemError() != noErr)
  135.             return MemError();
  136.         
  137.         pLong = (long*)((Ptr)*translationList + sizeof(FileTranslationList));
  138.     
  139.         *pLong++ = 2;                        // group1FromCount
  140.         *pLong++ = sizeof(FileTypeSpec);    // group1FromEntrySize
  141.         *pLong++ = 'TEXT';                    // group1FromTypes[1].format
  142.         *pLong++ = 0;                        // group1FromTypes[1].hint
  143.         *pLong++ = 0;                        // group1FromTypes[1].flags
  144.         *pLong++ = 'TEXT';                    // group1FromTypes[1].catInfoType
  145.         *pLong++ = 'ttxt';                    // group1FromTypes[1].catInfoCreator
  146.         *pLong++ = 'PIGL';                    // group1FromTypes[2].format
  147.         *pLong++ = 0;                        // group1FromTypes[2].hint
  148.         *pLong++ = 0;                        // group1FromTypes[2].flags
  149.         *pLong++ = 'PIGL';                    // group1FromTypes[2].catInfoType
  150.         *pLong++ = 'PIGH';                    // group1FromTypes[2].catInfoCreator
  151.         
  152.         *pLong++ = 2;                        // group1ToCount
  153.         *pLong++ = sizeof(FileTypeSpec);    // group1ToEntrySize
  154.         *pLong++ = 'TEXT';                    // group1ToTypes[1].format
  155.         *pLong++ = 0;                        // group1ToTypes[1].hint
  156.         *pLong++ = 0;                        // group1ToTypes[1].flags
  157.         *pLong++ = 'TEXT';                    // group1ToTypes[1].catInfoType
  158.         *pLong++ = 'ttxt';                    // group1ToTypes[1].catInfoCreator
  159.         *pLong++ = 'PIGL';                    // group1ToTypes[2].format
  160.         *pLong++ = 0;                        // group1ToTypes[2].hint
  161.         *pLong++ = 0;                        // group1ToTypes[2].flags
  162.         *pLong++ = 'PIGL';                    // group1ToTypes[2].catInfoType
  163.         *pLong++ = 'PIGH';                    // group1ToTypes[2].catInfoCreator
  164.     };
  165.  
  166.     return noErr;
  167. }; 
  168.  
  169.  
  170.  
  171.  
  172.  
  173. Boolean IsPigLatin(char* word)
  174. {
  175.     // go to end
  176.     while (*word++);
  177.     
  178.     // back up to last two chars in word
  179.     word -= 3;
  180.  
  181.     // to be pig latin last two chars of word must be 'ay'
  182.     return ( (*word++ == 'a') && (*word == 'y') );
  183. };
  184.  
  185.  
  186. /***********************************************************************************************
  187. *
  188. *    The purpose of DoIdentifyFile is to look at the data in a file and determine its FileType.
  189. *
  190. *    All installed File Translation Extensions are called to identify a file. 
  191. *    The value of docKind is initially set to NIL and each extension is called in succession.  
  192. *    Each has a chance to set docKind.  To handle the case when a document format can be a
  193. *    “subclasses” of another, an extension should only set docKind if docKind is already set 
  194. *    to a known “superclass” or NIL.  For example, because RTF is just ASCII characters “RTF” 
  195. *    is a subclass of “Plain Text”.  This means an RTF document could be identified as ‘RTF ’
  196. *    or ‘TEXT’.  Using the above rule, the extension could overwrite ‘TEXT’ with ‘RTF ’, but 
  197. *    not the other way around.  
  198. *
  199. *    return noTypeErr if you do not recognize the document format.
  200. *    
  201. ************************************************************************************************/
  202. pascal ComponentResult DoIdentifyFile(ComponentInstance self, const FSSpec* theDoc, FileType* docKind)
  203. {
  204. #pragma unused(self)
  205.     FInfo    fndrInfo;
  206.     OSErr    err;
  207.     short    srcRefNum = -1;
  208.     char    fileBuffer[256];
  209.     char    wordBuffer[256];
  210.     char*    nextCharInFileBuffer;
  211.     char*    nextCharInWordBuffer;
  212.     char    c;
  213.     Size    count;
  214.     
  215.     err = FSpGetFInfo(theDoc, &fndrInfo);
  216.     
  217.     if ( (err != noErr) || ((fndrInfo.fdType != 'TEXT')&&(fndrInfo.fdType != 'PIGL')) )
  218.     {
  219.         return noTypeErr;
  220.     };
  221.     
  222.     // assumme it is a text file, then figure out what kind it really is    
  223.     *docKind = 'TEXT';
  224.     
  225.     // open source
  226.     err = FSpOpenDF(theDoc, fsRdPerm, &srcRefNum);
  227.     if (err != noErr)
  228.         return noTypeErr;
  229.     
  230.     // get first chunk of data from file
  231.     count = 256;
  232.     err = FSRead(srcRefNum, &count, &fileBuffer[0]); 
  233.     FSClose(srcRefNum);
  234.     if ( (err != noErr) && (err !=  eofErr) )
  235.         return noTypeErr;
  236.         
  237.     nextCharInWordBuffer = &wordBuffer[0];
  238.     nextCharInFileBuffer = &fileBuffer[0];
  239.     while ( count-- > 0 )
  240.     {
  241.         c = *nextCharInFileBuffer++;
  242.         if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) )
  243.         {
  244.             *nextCharInWordBuffer++ = c;
  245.         }
  246.         else
  247.         {
  248.             // found something that cause a word break
  249.             if ( nextCharInWordBuffer != &wordBuffer[0] ) // is there something in the buffer?
  250.             {
  251.                 // see if it is pig latin
  252.                 *nextCharInWordBuffer = '\0';
  253.                 if ( IsPigLatin(wordBuffer) == false )
  254.                     return noErr;    // if not, must be FileType TEXT
  255.                     
  256.                 // reset for next word
  257.                 nextCharInWordBuffer = &wordBuffer[0];
  258.                 
  259.             };
  260.         };
  261.     };
  262.  
  263.     // every word in header was pig latin, so must be pig latin
  264.     *docKind = 'PIGL';
  265.     return noErr;
  266.  
  267. };
  268.  
  269.  
  270.  
  271. Boolean GetChar(short srcRefNum, char* c) 
  272. {
  273.     long    count = 1;
  274.     
  275.     return ( FSRead(srcRefNum, &count, c) == noErr ); 
  276. };
  277.  
  278.  
  279. void PutChar(short dstRefNum, char c) 
  280. {
  281.     long    count = 1;
  282.  
  283.     FSWrite(dstRefNum, &count, &c); 
  284. };
  285.  
  286.  
  287. Boolean IsConsonant(char c)
  288. {
  289.     switch (c)
  290.     {
  291.         case 'a':
  292.         case 'A':
  293.         case 'e':
  294.         case 'E':
  295.         case 'i':
  296.         case 'I':
  297.         case 'o':
  298.         case 'O':
  299.         case 'u':
  300.         case 'U':
  301.             return false;    
  302.         default:
  303.             return true;
  304.     };
  305. };
  306.  
  307.  
  308.  
  309. void ConvertToPigLatin(char* s)
  310. {
  311.     char*     sEnd;
  312.     short    sLength;
  313.     Boolean    capitalized;
  314.     
  315.     // remember if word is capitalized
  316.     capitalized = false;
  317.     if ( (*s >= 'A') && (*s <= 'Z') )
  318.     {
  319.         capitalized = true;
  320.         *s += ('a' - 'A'); // make it lowercase
  321.     };
  322.     
  323.     // find end of string
  324.     for (sEnd = s, sLength = 1; *sEnd != '\0'; sEnd++,sLength++);
  325.     
  326.     // if starts with consonants, move then to the end
  327.     if ( IsConsonant(*s) )
  328.     {
  329.         char*     t = s;
  330.         short    i = 0;
  331.         
  332.         // move consonants to end
  333.         while ( IsConsonant(*t) && (i<sLength) )
  334.         {
  335.             *sEnd++ = *t++;
  336.             i++;
  337.         };
  338.         
  339.         *sEnd = '\0';
  340.         BlockMove(t, s, sLength);    // move string to begin at start of buffer
  341.         sEnd -= i;                    // adjust end pointer back
  342.     };
  343.     
  344.     // if word was capitalized, make new word also
  345.     if (capitalized)
  346.         *s -= ('a' - 'A'); // make it uppercase
  347.     
  348.     // append "ay"
  349.     *sEnd++ = 'a';
  350.     *sEnd++ = 'y';
  351.     *sEnd++ = '\0';
  352. };
  353.  
  354.  
  355.  
  356. void ConvertFromPigLatin(char* s)
  357. {
  358.     char*     sEnd;
  359.     short    sLength;
  360.     Boolean    capitalized;
  361.     
  362.     // remember if word is capitalized
  363.     capitalized = false;
  364.     if ( (*s >= 'A') && (*s <= 'Z') )
  365.     {
  366.         capitalized = true;
  367.         *s += ('a' - 'A'); // make first letter lowercase
  368.     };
  369.     
  370.     // find end of string
  371.     for (sEnd = s, sLength = 1; *sEnd != '\0'; sEnd++,sLength++);
  372.     
  373.     // back up two to remove 'ay'
  374.     sEnd -= 2;
  375.     sLength -= 2;
  376.     *sEnd = '\0';
  377.     
  378.     // for short words do nothing
  379.     if ( (sLength > 3) || ((sLength == 3) && (s[0]=='e')) )
  380.     { 
  381.         // move last char to start
  382.         BlockMove(s, &s[1], sLength);  // need to shift rest of word
  383.         s[0] = s[sLength-1];    
  384.         s[sLength-1] = '\0';
  385.     };
  386.     
  387.     // special case 'th'
  388.     if ( (s[0] == 'h') && (s[sLength-2] == 't') )
  389.     {
  390.         BlockMove(s, &s[1], sLength);  // need to shift rest of word
  391.         s[0] = s[sLength-1];    
  392.         s[sLength-1] = '\0';
  393.     };    
  394.     
  395.     // if word was capitalized, make new word also
  396.     if (capitalized)
  397.         *s -= ('a' - 'A'); // make it uppercase
  398.     
  399. };
  400.  
  401.  
  402. #define progressAdvertismentResID    150
  403.  
  404. /***********************************************************************************************
  405. *
  406. *    The purpose of DoTranslateFile is to translate a file from one format to a file of another format.
  407. *    
  408. *    progressRefNum is a refum passed to the progress call back routines.
  409. *    srcDoc is the source document from which to read and translate.  
  410. *    srcType is the FileType of the source document as verified by DoIdentifyFile.
  411. *    srcTypeHint is the hint that was paired with srcType in your FileTranslationList.
  412. *    dstDoc is the destination document to which to write the translated file.
  413. *    dstType is the desired FileType of the translated file.
  414. *    dstTypeHint is the hint that was paired with dstType in your FileTranslationList.
  415. *    
  416. *    A note about 'hints'.  They are only hints.  Your extension must be able to operate without 
  417. *    them or if they are incorrect. 
  418. *    
  419. *    You can use all the standard File Manager and Memory Manager routines to accomplish the translation. 
  420. *    
  421. *    The first thing you should do is call SetTranslationAdvertisement.  It will draw the progress
  422. *    dialog window.  The PicHandle parameter is a handle to the PICT that will be drawn in 
  423. *    in the top of the progress dialog.  If it is NIL, no advertisment will be drawn, and the
  424. *    dialog will be shrunk down to eliminate the blank space. Note, this shrinking will also
  425. *    happen if you call UpdateTranslationProgress before SetTranslationAdvertisement.  The
  426. *    TranslationMgr will DisposeHandle the PICT when the translation is done.  Therefore,
  427. *    the PICT should be a non-purgable, non-resource handle.  If you store it as a resource 
  428. *    in your Translation Extension, use OpenComponentResFile and DetachResource.
  429. *
  430. *    You must periodically call the progress "call back" routine UpdateTranslationProgress.  
  431. *    You pass to it a number from one to one hundred that represents the 
  432. *    percentage of the translation that has been completed.
  433. *    The progress routine updates the progress status bar and handles the events a user can 
  434. *    do to cancel the translation.  If the user does cancel, that fact is returned to you by the
  435. *    progress routine.  You should abort the translation, clean up anything you have allocated
  436. *    and return userCancelErr.
  437. *
  438. ************************************************************************************************/
  439. pascal ComponentResult DoTranslateFile(ComponentInstance self, TranslationRefNum progressRefNum,
  440.                                             const FSSpec* srcDoc, FileType srcType, long srcTypeHint, 
  441.                                             const FSSpec* dstDoc, FileType dstType, long dstTypeHint)
  442. {
  443. #pragma unused(srcType)
  444. #pragma unused(srcTypeHint)
  445. #pragma unused(dstType)
  446. #pragma unused(dstTypeHint)
  447.     OSErr    err;
  448.     short    srcRefNum = -1;
  449.     short    dstRefNum = -1;
  450.     Size    srcSize;
  451.     Size    curPos;
  452.     Str255    wordBuffer;
  453.     char*    nextCharInBuffer;
  454.     char    c;
  455.     Boolean    canceled;
  456.  
  457.     // first thing to do is display progress dialog and show advertisment
  458.     {
  459.         Handle     advert;
  460.         short    myResFile;
  461.         
  462.         myResFile = OpenComponentResFile((Component)self);
  463.         if (myResFile != -1)
  464.         {
  465.             advert = GetResource('PICT', progressAdvertismentResID);
  466.             DetachResource(advert);
  467.             SetTranslationAdvertisement(progressRefNum, (PicHandle)advert);
  468.             CloseComponentResFile(myResFile);
  469.         };
  470.     }
  471.  
  472.     // open source
  473.     err = FSpOpenDF(srcDoc, fsRdPerm, &srcRefNum);
  474.     if (err != noErr)
  475.         goto abort;
  476.     err = GetEOF(srcRefNum, &srcSize);
  477.     if (err != noErr)
  478.         goto abort;
  479.  
  480.     // open destination
  481.     err = FSpOpenDF(dstDoc, fsRdWrPerm, &dstRefNum);
  482.     if (err != noErr)
  483.         goto abort;
  484.     
  485.     nextCharInBuffer = &wordBuffer[0];
  486.     curPos = 0;
  487.     while ( GetChar(srcRefNum, &c) )
  488.     {
  489.         curPos++;
  490.         if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) )
  491.         {
  492.             *nextCharInBuffer++ = c;
  493.         }
  494.         else
  495.         {
  496.             // is there something in the buffer?
  497.             if ( nextCharInBuffer != &wordBuffer[0] )
  498.             {
  499.                 // convert it to pig latin
  500.                 *nextCharInBuffer = '\0';
  501.                 if ( dstType == 'PIGL' )
  502.                     ConvertToPigLatin(wordBuffer);
  503.                 else
  504.                     ConvertFromPigLatin(wordBuffer);
  505.     
  506.                 // flush buffer
  507.                 nextCharInBuffer = wordBuffer;
  508.                 while ( *nextCharInBuffer )
  509.                     PutChar(dstRefNum, *nextCharInBuffer++);
  510.                 
  511.                 // reset for next word
  512.                 nextCharInBuffer = &wordBuffer[0];
  513.                 
  514.                 // update progress dialog
  515.                 UpdateTranslationProgress(progressRefNum, curPos*100/srcSize, &canceled);
  516.                 if (canceled)
  517.                 {
  518.                     err = userCanceledErr;
  519.                     goto abort;
  520.                 };
  521.             };
  522.             // write char that caused work break 
  523.             PutChar(dstRefNum,c);
  524.         };
  525.     };
  526.     // is there something left in the buffer?
  527.     if ( nextCharInBuffer != &wordBuffer[0] )
  528.     {
  529.         // convert it to pig latin
  530.         *nextCharInBuffer = '\0';
  531.         if ( dstType == 'PIGL' )
  532.             ConvertToPigLatin(wordBuffer);
  533.         else
  534.             ConvertFromPigLatin(wordBuffer);
  535.         
  536.         // flush buffer
  537.         nextCharInBuffer = wordBuffer;
  538.         while ( *nextCharInBuffer )
  539.             PutChar(dstRefNum, *nextCharInBuffer++);
  540.         
  541.         // reset for next word
  542.         nextCharInBuffer = &wordBuffer[0];
  543.         
  544.         // update progress dialog
  545.         UpdateTranslationProgress(progressRefNum, 100, &canceled);
  546.         if (canceled)
  547.         {
  548.             err = userCanceledErr;
  549.             goto abort;
  550.         };
  551.     };
  552.     
  553. abort:
  554.     if (srcRefNum != -1) FSClose(srcRefNum);
  555.     if (dstRefNum != -1) FSClose(dstRefNum);
  556.  
  557.     return err;
  558. };
  559.  
  560.  
  561.